from stopable_thread import StopableThread
from threading import Thread, Lock
from time import sleep
from gobject import idle_add
from signal import SIGTERM

#the first case should work for linux, the second for windows
try:
    from signal import SIGKILL
except:
    SIGKILL = SIGTERM

#the first case should work for linux, the others for windows, depending
#on what packages are available
try:
    from os import kill
    killsubproc = lambda proc, signal: kill(proc.pid, signal)
except:
    try:
        from win32api import TerminateProcess
        killsubproc = lambda proc, signal: TerminateProcess(int(proc._handle), -1)
    except:
        try:
            from ctypes.windll.kernel32 import TerminateProcess
            killsubproc = lambda proc, signal: TerminateProcess(int(proc._handle), -1)
        except:
            import os
            killsubproc = lambda proc, signal: os.popen('TASKKILL /PID '+str(proc.pid)+' /F')

changeLock1 = Lock() #this lock is used for threads
changeLock2 = Lock() #this lock is used for subprocess

class ThreadExecutionManager:
    def __init__(self):
        self.jobs = []
        self.active_job = None
        self.executionThread = None
        self.counter = 0
        #all functions in this list are called everytime anthing changes
        self.inscribedFunctions = []
    
    def inscribe(self, function):
        self.inscribedFunctions.append(function)
        
    def notify_inscribed(self):
        for i in self.inscribedFunctions:
            idle_add(i)
    
    def add_job(self, function, name='', *args):
        id = self.counter
        self.counter+=1
        changeLock1.acquire()
        self.jobs.append([function, args, name, id])
        changeLock1.release()
        self.run()
        self.notify_inscribed()
        return id
        
    def remove_job_by_name(self, jobname):
        buf = []
        changeLock1.acquire()
        for i in self.jobs:
            if i[2]!=jobname:
                buf.append(i)
        self.jobs = buf
        changeLock1.release()
        self.notify_inscribed()
        
    def remove_job_by_id(self, id):
        buf = []
        changeLock1.acquire()
        for i in self.jobs:
            if i[3]!=id:
                buf.append(i)
        self.jobs = buf
        changeLock1.release()
        self.notify_inscribed()
        
    def remove_all_jobs(self):
        changeLock1.acquire()
        self.jobs = []
        changeLock1.release()
        self.notify_inscribed()
        
    def terminate_active_job(self):
        if self.active_job:
            changeLock1.acquire()
            self.active_job[-1].terminate()
            changeLock1.release()
            self.notify_inscribed()
        
    def terminate_all(self):
        self.remove_all_jobs()
        self.terminate_active_job()
                
    def run(self):
        if self.executionThread:
            return
        self.executionThread = Thread(target=self.__execution__)
        self.executionThread.setDaemon(True)
        self.executionThread.start()
        
    def __execution__(self):
        
        #execute all jobs, one after another
        while True:
            #pop the next job out of the job-list
            changeLock1.acquire()
            self.active_job = self.jobs.pop(0)
            self.active_job.append(StopableThread(target=self.active_job[0], args=self.active_job[1]))
            changeLock1.release()
            self.notify_inscribed()
            
            #run the job and wait until it is finished
            self.active_job[-1].start()
            self.active_job[-1].join()
            
            #look if there's another job to do, otherwise exit
            changeLock1.acquire()
            if not self.jobs:
                self.executionThread = None
                self.active_job = None
                changeLock1.release()
                self.notify_inscribed()
                break
            changeLock1.release()
            
class SubprocessExecutionManager:
    def __init__(self):
        self.inscribedFunctions = []
        self.terminating = False
        self.counter = 0
        self.jobs = []
        
    def inscribe(self, function):
        self.inscribedFunctions.append(function)
        
    def notify_inscribed(self):
        for i in self.inscribedFunctions:
            idle_add(i)
            
    def add_job(self, executor, name='', *args):
        id = self.counter
        self.counter+=1
        t = Thread(target=self.__execute__, name = name, args=(executor, name, args, id))
        t.setDaemon(True)
        t.start()
        return id
    
    def terminate_by_id(self, id):
        #send process the terminate signal
        changeLock2.acquire()
        for j in self.jobs:
            if j[3]==id and j[0].poll() is None:
                killsubproc(j[0], SIGTERM)
                break
        changeLock2.release()
        
        #give process time to react
        sleep(0.5)
        
        #send process the kill signal
        changeLock2.acquire()
        for j in self.jobs:
            if j[3]==id and j[0].poll() is None:
                killsubproc(j[0], SIGKILL)
                break
        changeLock2.release()
    
    def terminate_all(self):
        self.terminating = True
        
        #send all processes the terminate signal
        changeLock2.acquire()
        for j in self.jobs:
            if j[0].poll() is None:
                killsubproc(j[0].pid, SIGTERM)
        changeLock2.release()
        
        #give processes time to react
        sleep(0.5)
        
        #send all processes the kill signal
        changeLock2.acquire()
        for j in self.jobs:
            if j[0].poll() is None:
                killsubproc(j[0].pid, SIGKILL)
        changeLock2.release()
        
        self.terminating = False
    
    def __execute__(self, executor, name, args, id):
        
        #get pid of process
        proc = executor.createProcessHandle(*args)
        
        #add process to the job list
        if self.terminating:
            return
        changeLock2.acquire()
        self.jobs.append((proc, name, args, id))
        changeLock2.release()
        self.notify_inscribed()
        
        #run subprocess
        executor.execute(*args)
        
        #remove job from the job list
        changeLock2.acquire()
        for j in self.jobs:
            if j[3]==id:
                self.jobs.remove(j)
                break
        changeLock2.release()
        self.notify_inscribed()